Create New Feature Screen
Before you dive in, it may be helpful to get familiar with the companion document Screen Structure and Wiring, which explains how the individual classes fit together. With that context in mind, this guide walks you through creating a new feature screen in the project. You can choose one of three paths:
- Using file templates — the recommended, fast and stable way
- Ask an AI agent — automated, the fastest way
- Manual setup — when you need full control or when templates aren't available
Option 1: Using File Templates (recommended)
- Ensure the UI Feature template is installed
- File → Manage IDE Settings → Import Settings...
- Import
file_teamplates.zipfrom the baselines-kmp root dir
- Right-click the destination package → New → UI Feature
- Enter the feature name (e.g., Profile, Settings)
- Review generated files and fix missing imports
- Done 🎉 — your feature is wired into the app
Option 2: Setup via AI Agent
- Open chat with your AI assistant and make sure it's aware of
./AGENTS.mdfile. - Ask to create new feature screen
- Provide all the necessary info requested by the AI agent
- Done 🎉 — your feature is wired into the app
Option 3: Manual Setup
- Create
*UiEvent
kotlin1sealed interface ProfileUiEvent : UiEvent {2 data object PerformLogout : ProfileUiEvent {3 override val dispatchPolicy = UiEventDispatchPolicy.ThrottleFirst()4 }5}
Use ThrottleFirst for click-like one-shot events. Use DebounceLatest only for delayed latest-value effects such as search or filtering. If a text field is controlled by ViewModel state, keep the visible input state immediate and debounce a separate effect event instead of the input-state event itself.
- Create
*UiState
kotlin1@Immutable2data class ProfileUiState(3 override val eventSink: (ProfileUiEvent) -> Unit,4) : UiState<ProfileUiEvent>
- Create
*ViewModel
Use plain @Inject for the normal case. Switch to @AssistedInject only when the ViewModel needs runtime arguments that cannot come from the DI graph.
kotlin1@Inject2@ContributesIntoMap(AppScope::class, binding<ViewModel>())3@ViewModelKey(ProfileViewModel::class)4class ProfileViewModel : ViewModel(), Mvvm<ProfileUiEvent, ProfileUiState> {56 private val eventSink = createEventSink(::handleEvent)78 @Composable9 override fun state() = ProfileUiState(10 eventSink = eventSink,11 )1213 private fun handleEvent(event: ProfileUiEvent) {14 when (event) {15 ProfileUiEvent.PerformLogout -> handleLogout()16 }17 }1819 private fun handleLogout() {20 /* handle action */21 }22}
- Create
*Screen
kotlin1@Composable2fun ProfileScreen(onLogoutClicked: () -> Unit) {3 /* UI */4}
- Create
*Route
kotlin1@Composable2fun ProfileRoute(viewModel: ProfileViewModel) {3 val state = viewModel.state()4 val eventSink = state.eventSink5 ProfileScreen(6 onLogoutClicked = { eventSink(ProfileUiEvent.PerformLogout) }7 )8}
- Add DI
*Module
kotlin1@ContributesTo(UiScope::class)2interface ProfileUiModule {34 @Provides5 @IntoSet6 fun provideProfileNavGraphEntry(): NavGraphEntry = NavGraphEntry {7 composable<AppNavRoutes.Profile> {8 ProfileRoute(metroViewModel())9 }10 }11}
- Ensure the feature module is a dependency of app/compose module
- Done 🎉 — your feature is wired into the app